package test;


import jdk.nashorn.api.scripting.NashornScriptEngine;
import jdk.nashorn.api.scripting.ScriptObjectMirror;

import javax.script.*;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

public class NashornVegaRenderer {

    /**
     * 渲染等待超时时间（毫秒）
     */
    private static final int RENDER_WAIT_TIMEOUT = 1000 * 60;
    /**
     * 渲染等待轮询间隔(毫秒)
     */
    private static final int RENDER_WAIT_INTERVAL = 200;

    private static final ScriptContext sc = new SimpleScriptContext();
    private static final ScheduledExecutorService globalScheduledThreadPool = Executors.newScheduledThreadPool(2);
    private final NashornScriptEngine engine;

    private NashornVegaRenderer() {
        engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");
        Bindings bindings = new SimpleBindings();

        sc.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
        sc.getBindings(ScriptContext.ENGINE_SCOPE).putAll(bindings);
        sc.setAttribute("__IS_SSR__", true, ScriptContext.ENGINE_SCOPE);
        sc.setAttribute("__NASHORN_POLYFILL_TIMER__", globalScheduledThreadPool, ScriptContext.ENGINE_SCOPE);
        engine.setBindings(sc.getBindings(ScriptContext.ENGINE_SCOPE), ScriptContext.ENGINE_SCOPE);

        try {
            engine.eval(new FileReader("vega-nashorn/index.js"));
        } catch (ScriptException e) {
            e.printStackTrace();
            System.out.println("load javascript failed.");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取单例
     *
     * @return
     */
    private static synchronized NashornVegaRenderer getInstance() {
        return NashornVegaSingletonHolder.INSTANCE;
    }

    /**
     * 将vega语法描述渲染成SVG
     *
     * @param vegaSpec
     * @return
     */
    public static String renderVegaSVG(String vegaSpec) {
        NashornVegaRenderer engine = NashornVegaRenderer.getInstance();
        ScriptObjectMirror promise = null;
        try {
            promise = (ScriptObjectMirror) engine.callRender(vegaSpec);
        } catch (ScriptException | NoSuchMethodException e) {
            e.printStackTrace();
            return null;
        }

        AtomicReference<String> svgRendered = new AtomicReference<>("");
        AtomicReference<Boolean> resolved = new AtomicReference<>(false);
        promise.callMember("then", (Consumer<Object>) o -> {
            svgRendered.set((String) o);
            resolved.set(true);
        });
        ScriptObjectMirror nashornEventLoop = engine.getEventLoop();

        int totalWaitTime = 0;
        while (Objects.equals(true, !resolved.get()) && totalWaitTime < RENDER_WAIT_TIMEOUT) {
            // 触发事件循环
            nashornEventLoop.callMember("process");
            try {
                Thread.sleep(RENDER_WAIT_INTERVAL);
            } catch (InterruptedException e) {
                // do nothing
                Thread.currentThread().interrupt();
            }
            totalWaitTime = totalWaitTime + RENDER_WAIT_INTERVAL;
        }
        return svgRendered.get();
    }

    private ScriptObjectMirror getEventLoop() {
        return (ScriptObjectMirror) engine.getBindings(ScriptContext.ENGINE_SCOPE).get("nashornEventLoop");
    }

    /**
     * js引擎方法调用， 调用渲染入口函数
     *
     * @param input
     * @return
     */
    private Object callRender(Object... input) throws ScriptException, NoSuchMethodException {
        return engine.invokeFunction("nashornRender", input);
    }

    private static class NashornVegaSingletonHolder {
        private static final NashornVegaRenderer INSTANCE = new NashornVegaRenderer();
    }
}